import java.util.ArrayList;
import java.util.Hashtable;

/**
 * Created by IntelliJ IDEA.
 * User: Rajeev
 * Date: Dec 27, 2009
 * Time: 2:55:07 PM
 * To change this template use File | Settings | File Templates.
 */
public class RecursiveDescent {

    Element root = null;
    Node dsdtree = null;
    private String metaprefix;

    public RecursiveDescent(Element root) {
        this.root = root;
    }

    public void start() throws Exception {
        dsdtree = schema(this.root);

        PrettyPrinter pp = new PrettyPrinter();
        pp.print(dsdtree, "printout.txt");

    }

    boolean invalid(Element e) {
        if (metaprefix != null) {
            System.out.println("metap:" + metaprefix);
            String temp = e.getName().toLowerCase();
            if (temp.indexOf(metaprefix) == 0) {
                return false;
            } else if (temp.equals("import")) {
                return false;
            }
        }
        return true;
    }

    boolean e(String first, String second) {
        if (first == null || second == null) {
            return false;
        }
        if (first.equals(second)) {
            return true;
        }
        return false;
    }


    /*
 SCHEMA	 ::=  	<dsd  ( root="PENAME" )? >
( RULE  |  DEFINITION  |  SCHEMA )*
</dsd>
    */
    private Node schema(Element e) throws SyntaxException {
        Node n = new Node();

        // check for meta prefix
        //  http://www.brics.dk/DSD/2.0/meta

         Hashtable h = e.getAttributes();
        try {
        for (Object att: h.keySet())  {
            String val = (String) h.get(att);
//            System.out.println(val);
            if ("http://www.brics.dk/DSD/2.0/meta".equals(val)) {
                try {
                    String x = (String) att;
                    this.metaprefix = x.split(":")[1].toLowerCase();
                } catch (Exception e1) {
                }
            }
        }
        } catch (Exception exc) {

        }

        if (e(Tags.dsd, e.getName())) {
            n.setType("dsd");
            n.meta = "SCHEMA";
            Node c = null;

            for (Element child : e.getChildren()) {
                if ((c = rule(child)) != null || (c = definition(child)) != null || (c = schema(child)) != null) {
                    n.addChild(c);
                } else {
                    // todo enable if needed.
                    validate(child);
//                      throw new SyntaxException("INvalid tag inside schema.");
                }

            }
            return n;

        } else {
            return null;
        }
    }

    void validate(Element e) throws SyntaxException {
        if (invalid(e)) {
            throw new SyntaxException("invalid tag:" + e.getName());
        }
    }


    /*
     RULE	 ::=  	<declare> DECLARATION* </declare>
     | 	<require> BOOLEXP* </require>
     | 	<if> BOOLEXP RULE* </if>
     | 	<rule ref="PENAME"/>
     | 	UNIQUE
     | 	POINTER
     */
    Node rule(Element e) throws SyntaxException {
        Node n = new Node();
        n.meta = "RULE";
        String tag = e.getName();
        n.type = tag;
        Node child = null;
        if (e("declare", tag)) {
            for (Element xc : e.getChildren()) {
                if ((child = declaration(xc)) != null) {
                    n.addChild(child);
                } else {
                    if (invalid(e)) {
                        throw new SyntaxException("Invalid tag inside RULE.");

                    }
                }
            }

            return n;
        } else if (e("require", tag)) {
            for (Element xc : e.getChildren()) {
                if ((child = boolexp(xc)) != null) {
                    n.addChild(child);
                } else {
                    if (invalid(e)) {
                        throw new SyntaxException("Invalid tag inside RULE.");
                    }
                }
            }

            return n;
        } else if (e("if", tag)) {
            ArrayList<Element> list = this.cloneChildren(e);
            child = boolexp(list.get(0));
            if (child == null) {
                throw new SyntaxException("Invalid tag inside rule.");

            }
            n.addChild(child);
            list.remove(0);
            for (Element xc : list) {
                child = rule(xc);
                if (child != null) {
                    n.addChild(child);
                } else {
                    validate(e);
                }
            }
            return n;
        } else if (e("rule", tag)) {
            String at = e.getAttribute("ref");
            if (at == null) {
//                out("ref attribute null");
                return null;
            } else {
                Attribute a = new Attribute();

                a.type = "ref";    // todo validate pename
                a.value = at;
                n.addAttribute(a);

            }
            n.inline = true;
            return n;
        } else {
            // unique pointer
            if ((child = unique(e)) != null || (child = pointer(e)) != null) {
                return child;
//            } else {
//                validate(e);
            }
        }


        return null;
    }

    Attribute pename(String name, Element e) throws SyntaxException {

        // todo validate pename.
        String at = e.getAttribute(name);
        if (at != null) {
            Attribute a = new Attribute();
            a.type = name;
            a.value = at;
            return a;
        }
        return null;
    }


    ArrayList<Element> cloneChildren(Element e) {
        return (ArrayList<Element>) e.getChildren().clone();
    }

    void out(String param) throws SyntaxException {
        throw new SyntaxException(param);
    }


    /*
    DECLARATION	 ::=  	ATTRIBUTEDECL
	| 	<required> ATTRIBUTEDECL* </required>
	| 	<contents> ( REGEXP  |  NORMALIZE  |  CONTENTSDEFAULT )* </contents>
     */
    Node declaration(Element e) throws SyntaxException {
        Node n = new Node();
        n.meta = "DECLARATION";
        Node child = null;
        String tag = e.getName();
        n.type = tag;
        if ((child = attributedecl(e)) != null) {
//            n.addChild(child);
            return child;

        } else if (e("required", tag)) {
            for (Element ch : e.getChildren()) {
                child = attributedecl(ch);
                if (child != null) {
                    n.addChild(child);
                } else {
                    validate(e);
                }
            }
            return n;

        } else if (e("contents", Tags.contents)) {
            for (Element ch : e.getChildren()) {
                if ((child = regexp(ch)) != null || (child = normalize(ch)) != null || (child = contentsDefault(ch)) != null) {
                    n.addChild(child);
                } else {
                    validate(ch);
                }
            }
            return n;

        } else {
//            validate(e);
        }
        return null;
    }


    /*
   ATTRIBUTEDECL	 ::=  	<attribute  ( name="PANAME" )? ( type="ATTRTYPE" )? >
( REGEXP  |  NORMALIZE  |  ATTRIBUTEDEFAULT )*
</attribute>
    */
    Node attributedecl(Element e) throws SyntaxException {

        if (e(Tags.attribute, e.getName())) {
            Node n = new Node();
            n.meta = Descriptions.ATTRIBUTEDECL;
            n.type = Tags.attribute;
            Attribute name = this.paname("name", e); // todo paname validate.
            Attribute type = this.attrtype("type", e); // todo paname validate.
            if (name != null) {
                n.addAttribute(name);
            }
            if (type != null) {
                n.addAttribute(type);
            }
            Node child = null;

            for (Element ch : e.getChildren()) {
                if ((child = regexp(ch)) != null || (child = normalize(ch)) != null || (child = attributeDefault(ch)) != null) {
                    n.addChild(child);
                } else {
                    validate(ch);
                }

            }

            return n;
        }
        return null;
    }

    Attribute paname(String name, Element e) throws SyntaxException {
        // paname
        String at = e.getAttribute(name);
        if (at != null) {
            Attribute a = new Attribute();
            a.type = name;
            a.value = at;
            // todo validate.
            return a;
        }
        return null;
    }

    /*
    ATTRTYPE	 ::=  	string  |  qname  |  qaname
     */
    Attribute attrtype(String name, Element e) throws SyntaxException {
        String at = e.getAttribute(name);
        if (at != null) {
            if (e(Tags.string, at) || e(Tags.qname, at) || e(Tags.qaname, at)) {
                Attribute a = new Attribute();
                a.type = name;
                a.value = at;

                return a;
            } else {
                out("invalid attrtype.");
            }
        }
        return null;
    }

    /*
    BOOLEXP	 ::=  	<and> BOOLEXP* </and>
	| 	<or> BOOLEXP* </or>
	| 	<not> BOOLEXP </not>
	| 	<imply> BOOLEXP BOOLEXP </imply>
	| 	<equiv> BOOLEXP* </equiv>
	| 	<one> BOOLEXP* </one>
	| 	<parent> BOOLEXP </parent>
	| 	<ancestor> BOOLEXP </ancestor>
	| 	<child> BOOLEXP </child>
	| 	<descendant> BOOLEXP </descendant>
	| 	<this/>
	| 	<element ( name="PENAME" )? />
	| 	<attribute ( name="PANAME" )? > REGEXP? </attribute>
	| 	<contents> REGEXP* </contents>
	| 	<boolexp ref="PENAME"/>
     */
    Node boolexp(Element e) throws SyntaxException {
        Node n = new Node();
        n.meta = Descriptions.BOOLEXP;
        n.type = e.getName();
        Node child = null;
        if (equalsOR(e.getName(), Tags.and, Tags.or, Tags.equiv, Tags.one)) {
            for (Element ch : e.getChildren()) {
                if ((child = boolexp(ch)) != null) {
                    n.addChild(child);
                } else {
                    validate(ch);
                }
            }

        } else if (equalsOR(e.getName(), Tags.not, Tags.parent, Tags.ancestor, Tags.child, Tags.descendant)) {
            boolean found = false;
            for (Element ch : e.getChildren()) {
                if ((!found) && (child = boolexp(ch)) != null) {
                    n.addChild(child);
                    found = true;
                } else {
                    validate(ch);
                }
            }

            if (!found) {
                out("no boolexp inside boolexp.");
            }

        } else if (e(e.getName(), Tags.imply)) {
            int count = 0;
            for (Element ch : e.getChildren()) {
                if ((count < 2) && (child = boolexp(ch)) != null) {
                    n.addChild(child);
                    count++;
                } else {
                    validate(ch);
                }
            }

            if (count < 2) {
                out("no 2 boolexps inside boolexp.");
            }
        } else if (e(e.getName(), Tags.This)) {
            n.inline = true;
            validateInline(e);
        } else if (e(e.getName(), Tags.element)) {
            Attribute a = this.pename("name", e);
            n.addAttribute(a);
            validateInline(e);

        } else if (e(e.getName(), Tags.attribute)) {
            Attribute a = this.paname("name", e);
            n.addAttribute(a);

            boolean found = false;
            for (Element ch : e.getChildren()) {
                if ((!found) && (child = regexp(ch)) != null) {
                    n.addChild(child);
                    found = true;
                } else {
                    validate(ch);
                }
            }

        } else if (e(e.getName(), Tags.contents)) {

            for (Element ch : e.getChildren()) {
                if ((child = regexp(ch)) != null) {
                    n.addChild(child);
                } else {
                    validate(ch);
                }
            }


        } else if (e(e.getName(), Tags.boolexp)) {

            Attribute a = pename("ref", e);
            if (a != null) {
                n.addAttribute(a);
            } else {
                out("NO ref attribute inside boolexp tag.");
            }
            validateInline(e);

        } else {

            // todo validate if needed..

        }


        return n;
    }


    void validateInline(Element e) throws SyntaxException {
        if (e.getChildren() != null && e.getChildren().size() > 0) {
            for (Element em : e.getChildren()) {
                validate(em);
            }
        }
    }

    /*
    REGEXP	 ::=  	<sequence> REGEXP* </sequence>
	| 	<optional> REGEXP </optional>
	| 	<complement> REGEXP </complement>
	| 	<union> REGEXP* </union>
	| 	<intersection> REGEXP* </intersection>
	| 	<minus> REGEXP REGEXP </minus>
	| 	<repeat ( ( number="NUMERAL" )?  |  ( min="NUMERAL" )? ( max="NUMERAL" )? ) > REGEXP </repeat>
	| 	<string ( value="VALUE" )? />
	| 	<char ( ( set="VALUE" )?  |  min="CHAR" max="CHAR" ) />
	| 	<stringtype ref="PENAME"/>
	| 	<contenttype ref="PENAME"/>
	| 	BOOLEXP
     */
    Node regexp(Element e) throws SyntaxException {
        String tag = e.getName();
        Node n = new Node();
        n.meta = Descriptions.REGEXP;
        n.type = tag;
        Node child = null;
        if (equalsOR(tag, Tags.sequence, Tags.union, Tags.intersection)) {

            for (Element ch : e.getChildren()) {
                if ((child = regexp(ch)) != null) {
                    n.addChild(child);
                } else {
                    validate(ch);
                }
            }


        } else if (equalsOR(tag, Tags.optional, Tags.complement)) {
            boolean found = false;
            for (Element ch : e.getChildren()) {
                if ((!found) && (child = regexp(ch)) != null) {
                    n.addChild(child);
                    found = true;
                } else {
                    validate(ch);
                }
            }

            if (!found) {
                out("no regexp inside regexp.");
            }


        } else if (e(Tags.minus, tag)) {
            int count = 0;
            for (Element ch : e.getChildren()) {
                if ((count < 2) && (child = regexp(ch)) != null) {
                    n.addChild(child);
                    count++;
                } else {
                    validate(ch);
                }
            }

            if (count < 2) {
                out("no 2 boolexps inside boolexp.");
            }


        } else if (e(Tags.repeat, tag)) {
//            | 	<repeat ( ( number="NUMERAL" )?  |  ( min="NUMERAL" )? ( max="NUMERAL" )? ) > REGEXP </repeat>
            n.addAttribute(this.numeral("number", e));
            n.addAttribute(this.numeral("min", e));
            n.addAttribute(this.numeral("max", e));

            boolean found = false;
            for (Element ch : e.getChildren()) {
                if ((!found) && (child = regexp(ch)) != null) {
                    n.addChild(child);
                    found = true;
                } else {
                    validate(ch);
                }
            }

            if (!found) {
                out("no regexp inside repeat..");
            }


        } else if (e(Tags.string, tag)) {

            /*
           | 	<string ( value="VALUE" )? />
| 	<char ( ( set="VALUE" )?  |  min="CHAR" max="CHAR" ) />
| 	<stringtype ref="PENAME"/>
| 	<contenttype ref="PENAME"/>
| 	BOOLEXP
            */

            n.addAttribute(value("value", e));
            validateInline(e);
        } else if (e(Tags.Char, tag)) {
            Attribute a = value("set", e);
            if (a != null) {
                n.addAttribute(a);
                if (e.getAttribute("min") != null || e.getAttribute("max") != null) {
                    out("both set || min max can't exist together.");
                }
            } else {
                if (e.getAttribute("min") != null ^ e.getAttribute("max") != null) {    // todo valideate use of xor.
                    out("only one of min and max attributes can't exist alone.");
                }
                n.addAttribute(Char("min", e));
                n.addAttribute(Char("max", e));

            }
            validateInline(e);
        } else if (e(Tags.stringtype, tag)) {

            validateInline(e);
            Attribute a = pename("ref", e);
            if (a == null) {
                out("pename attribute doesn't exist");
            }
            n.addAttribute(a);


        } else if (e(Tags.contenttype, tag)) {
            validateInline(e);
            Attribute a = pename("ref", e);
            if (a == null) {
                out("pename attribute doesn't exist");
            }
            n.addAttribute(a);

        } else {
            child = boolexp(e);
            if (child != null) {
                return child;
            }
            return null;
        }

        return n;
    }

    private Attribute Char(String name, Element e) {
        String at = e.getAttribute(name);

        if (at != null) {
            Attribute a = new Attribute();
            a.type = name;
            a.value = at;
            return a; // todo validate

        }

        return null;
    }

    private Attribute value(String name, Element e) {
        String at = e.getAttribute(name);
          // no need to check....already evaluated by xml
        if (at != null) {
            Attribute a = new Attribute();
            a.type = name;
            a.value = at;
            return a; // todo validate

        }

        return null;
    }

    Attribute numeral(String name, Element e) throws SyntaxException {
        String at = e.getAttribute(name);
        if (at != null) {
            int start = '0';
            int end = '9';
//            System.out.println("start::" + start);
//            System.out.println("endd::" + end);
            for (char c: at.toCharArray()) {
                int x = c;
                 if (x > end || x < start) {
                     out("Invalid numeral attribute");
                 }
            }
            Attribute a = new Attribute();    // todo validate for numeral
            a.type = name;
            a.value = at;
            return a;
        }
        return null;
    }

    /*
    DEFINITION	 ::=  	<rule id="PENAME"> RULE* </rule>
   | 	<contenttype id="PENAME"> REGEXP </contenttype>
   | 	<stringtype id="PENAME"> REGEXP </stringtype>
   | 	<boolexp id="PENAME"> BOOLEXP </boolexp>
    */
    Node definition(Element e) throws SyntaxException {
        String tag = e.getName();
        Node n = new Node();
        n.meta = Descriptions.REGEXP;
        n.type = tag;
        Node child = null;

        if (e(Tags.rule, tag)) {
            Attribute a = pename("id", e);
            if (a != null) {
                n.addAttribute(a);
            } else {
                out("no id attribute inside rule ");
            }
            for (Element ch : e.getChildren()) {
                if ((child = rule(ch)) != null) {
                    n.addChild(child);
                } else {
                    validate(ch);
                }
            }
        } else if (equalsOR(tag, Tags.contenttype, Tags.stringtype)) {
            boolean found = false;
            for (Element ch : e.getChildren()) {
                if ((!found) && (child = regexp(ch)) != null) {
                    n.addChild(child);
                    found = true;
                } else {
                    validate(ch);
                }
            }

            if (!found) {
                out("regexp inside definition.");
            }

            Attribute a = pename("id", e);
            if (a != null) {
                n.addAttribute(a);
            } else {
                out("no id attribute inside contenttype/stringtype ");
            }

        } else if (e(Tags.boolexp, tag)) {
            Attribute a = pename("id", e);
            if (a != null) {
                n.addAttribute(a);
            } else {
                out("no id attribute inside boolexp ");
            }

            boolean found = false;
            for (Element ch : e.getChildren()) {
                if ((!found) && (child = boolexp(ch)) != null) {
                    n.addChild(child);
                    found = true;
                } else {
                    validate(ch);
                }
            }

            if (!found) {
                out("no boolexp inside boolexp.");
            }


        } else {
            return null;
        }


        return n;
    }

    /*
    NORMALIZE	 ::=  	<normalize  ( whitespace="WHITESPACE" )?  ( case="CASE" )? />
     */
    Node normalize(Element e) throws SyntaxException {
        String tag = e.getName();
        Node n = new Node();
        n.meta = Descriptions.REGEXP;
        n.type = tag;
//        Node child = null;
        if (e(tag, Tags.normalize)) {
            n.addAttribute(whitespace(e));
            n.addAttribute(Case(e));
            validateInline(e);
            return n;

        }


        return null;
    }


    /*
   WHITESPACE	 ::=  	preserve  |  compress  |  trim
    */
    Attribute whitespace(Element e) throws SyntaxException {
        Attribute a = new Attribute();
        String at = e.getAttribute(Tags.whitespace);
        if (at != null) {
            if (e(at, Tags.preserve) || e(at, Tags.compress) || e(at, Tags.trim)) {
                a.type = Tags.whitespace;
                a.value = at;
                return a;
            } else {
                out("invalid whitespace attribute");
            }
        }
        return null;
    }

    /*
    CASE	 ::=  	preserve  |  upper  |  lower
     */
    Attribute Case(Element e) throws SyntaxException {
        Attribute a = new Attribute();
        String at = e.getAttribute(Tags.Case);
        if (at != null) {
            if (e(at, Tags.preserve) || e(at, Tags.upper) || e(at, Tags.lower)) {
                a.type = Tags.Case;
                a.value = at;
                return a;
            } else {
                out("invalid case attribute");
            }
        }
        return null;
    }

    /*
    ATTRIBUTEDEFAULT	 ::=  	<default value="VALUE" />
     */
    Node attributeDefault(Element e) throws SyntaxException {
        if (e(e.getName(), Tags.Default)) {
            Node n = new Node();
            n.meta = Descriptions.ATTRIBUTEDEFAULT;
            n.inline = true;
            n.type = Tags.Default;
            Attribute a = getAttribute("value", e);

            if (a != null) {
                n.addAttribute(a);
                return n;
            }
        }

        return null;
    }

    Attribute getAttribute(String name, Element e) {
        Attribute a = new Attribute();
        String at = e.getAttribute(name);
        if (at != null) {
            a.type = name;
            a.value = at;
            return a;
        }
        return null;
    }

    /*
    CONTENTSDEFAULT	 ::=  	<default> ANYCONTENTS </default>
    */
    Node contentsDefault(Element e) throws SyntaxException {
        // todo validate any content
        if (e(Tags.Default, e.getName())) {
            Node n = new Node();
            n.type = Tags.Default;
            n.meta = Descriptions.CONTENTSDEFAULT;
            n.content = e.getContent();
            return n;

        }
        return null;
    }

    /*
UNIQUE	 ::= 	<unique ( key="VALUE" )? >
  ( BOOLEXP FIELD* | ( <select> BOOLEXP FIELD* </select> )* )
</unique>

     */
    Node unique(Element e) throws SyntaxException {
        // todo this is the damn thing.

        String tag = e.getName();
        Node n = new Node();
        n.meta = Descriptions.REGEXP;
        n.type = tag;
        Node child = null;

        if (e(Tags.unique, tag)) {
            n.addAttribute(value("key", e));

            boolean select = false;
            boolean childfound = false;
            for (Element ch : e.getChildren()) {

                if (e(ch.getName(), "select")) {
                    select = true;
                }

                if (select) {
                    if (e("select", ch.getName())) {
                        Node snode = new Node();
                        snode.type = "select";
                        snode.meta = "UNIQUE:select";
                        Node inner = null;
                        boolean selectChildFound = false;
                        for (Element nc : ch.getChildren()) {
                            if (!selectChildFound) {
                                inner = boolexp(nc);
                                if (inner != null) {
                                    snode.addChild(inner);
                                } else {
                                    validate(nc);
                                }

                            } else {
                                inner = field(nc);
                                if (inner != null) {

                                    snode.addChild(inner);
                                } else {
                                    validate(nc);
                                }

                            }
                        }

                        n.addChild(snode);
                    } else {
                        validate(e);  // todo is this correct?
//                        out("Can't have selects mixed with others.");
                    }

                } else {
                    if (!childfound) {
                        child = boolexp(ch);
                        if (child != null) {
                            n.addChild(child);
                        } else {
                            validate(ch);
                        }
                    } else {
                        child = field(ch);
                        if (child != null) {
                            n.addChild(child);
                        } else {
                            validate(ch);
                        }
                    }


                }

            }
            return n;
        }


        return null;
    }


    /*
    POINTER	 ::= 	<pointer ( key="VALUE" )? > BOOLEXP? FIELD* </pointer>
     */
    Node pointer(Element e) throws SyntaxException {

        if (e(Tags.pointer, e.getName())) {
            Node n = new Node();
            n.type = e.getName();
            n.meta = Descriptions.POINTER;

            Attribute a = getAttribute(Tags.key, e);
            if (a != null) {
                n.addAttribute(a);
            }

            Node child = boolexp(e.getChildren().get(0));
            ArrayList<Element> list = this.cloneChildren(e);
            if (child != null) {
                n.addChild(child);
                list.remove(0);
            }

            for (Element ch : list) {
                if ((child = field(ch)) != null) {
                    n.addChild(child);
                } else {
                    validate(ch);
                }
            }
            return n;
        }

        return null;
    }


    /*
    FIELD	 ::=  	<attributefield name="PANAME" ( type="ATTRTYPE" )? > BOOLEXP? </attributefield>
	| 	<chardatafield ( type="ATTRTYPE" )? > BOOLEXP? </chardatafield>
     */
    Node field(Element e) throws SyntaxException {
        String tag = e.getName();
        Node n = new Node();
        n.meta = Descriptions.REGEXP;
        n.type = tag;
        Node child = null;
        Attribute a = null;
        if (equalsOR(tag, Tags.attributefield, Tags.chardatafield)) {
            a = attrtype("type", e);
            n.addAttribute(a);
            if (e(Tags.attributefield, tag)) {
                // paname compulsary
                a = paname("name", e);
                if (a == null) {
                    out("NO paname inside attributefield");

                }
                n.addAttribute(a);
            }

            boolean found = false;
            for (Element ch : e.getChildren()) {
                if ((!found) && (child = boolexp(ch)) != null) {
                    n.addChild(child);
                    found = true;
                } else {
                    validate(ch);
                }
            }

//            if (!found) {
//                out("no boolexp inside boolexp.");
//            }
            return n;
        }


        return null;
    }


    // returns true if x matches one of the targets.
    private boolean equalsOR(String x, String... targets) {
        for (String item : targets) {
            if (x.equalsIgnoreCase(item)) {
                return true;
            }
        }
        return false;
    }

    /////////////////////////////////////////////////////////////
    // util methods

    // compares two consecutive items. number of params must be even.

    private boolean equalsConsecutive(String... items) {
        if (items.length % 2 == 1) {
            return false;
        }
        for (int i = 0; i < items.length; i += 2) {
            if (!items[i].equalsIgnoreCase(items[i + 1])) {
                return false;
            }
        }
        return true;
    }

}
